iT邦幫忙

2024 iThome 鐵人賽

DAY 27
0
Modern Web

Rust 的戰國時代:探索網頁前端工具的前世今生系列 第 27

Day 27:Rust 學習筆記 (4) - 用猜數字遊戲來學什麼是 traits

  • 分享至 

  • xImage
  •  

前言

前面有提到學習的第三、第四階段希望來寫一個簡單函式與 Rust CLI 工具,因為時間也所剩不多,今天就結合在一起來學吧!

官方教學文件中有個經典的入門猜數字遊戲範例,再來看看其中有什麼可以再補上的基本語法與觀念,或許能碰上第一個坎中的所有權。

實作

建立專案

可以沿用昨天的專案來改寫其中的 main.rs,或直接用 cargo new 再建立一個新專案都可以:

$ cargo new guessing_game
$ cd guessing_game
$ code .

💡 BTW,code . 是 VS Code 快速開啟當前資料夾的指令,或用自己習慣的編輯器開啟專案也可以

安裝 rand 這個 crate,因為 Rust 本身沒也產亂數的函式庫,這裡會需要安裝套件:

$ cargo add rand

體驗猜數字遊戲範例

參考官方的範例先試著放到 main.rs 中,並補上一些簡單的邏輯說明:

// main.rs
// 導入需要的函式庫到這個作用域中
use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
    println!("請猜測一個 1-100 間的數字:");

    // 產生一個隨機數字
    let secret_number = rand::thread_rng().gen_range(1..=100);

    // while loop 直到中斷
    loop {
        println!("請輸入你的猜測數字。");

        // 宣告一個變數來接使用者從 CLI 上的輸入
        let mut guess = String::new();

        // 利用 io 標準函式庫來讀取值
        // 並寫入到 guess 中
        io::stdin().read_line(&mut guess).expect("讀取該行失敗");

        // 字串轉數字
        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

        println!("你的猜測數字:{guess}");

        // 比對數字並印出對應結果
        match guess.cmp(&secret_number) {
            Ordering::Less => println!("太小了!"),
            Ordering::Greater => println!("太大了!"),
            Ordering::Equal => {
                println!("獲勝!");
                break;
            }
        }
    }
}

嘗試用 cargo run 編譯並執行試試效果:

demo

可以看到這段程式執行後可以在 CLI 上與使用者互動,玩類似終極密碼的遊戲,那麼以下我們就來補一下不熟悉的觀念吧。

猜數字遊戲解析

什麼是 rand::Rng

首先看到最開頭函式庫引入的地方,我有點好奇 rand::Rng 是什麼,這裡去 crates.io 上查 rand 這個 crate 的文件,會寫道:

Easy random value generation and usage via the Rng, SliceRandom and IteratorRandom traits

這個 traits 又是什麼呢?如果點進 Rng 的介紹會看到這樣的程式:

pub trait Rng: RngCore {
    fn gen<T>(&mut self) -> T
    where
        Standard: Distribution<T>,
    { ... }
    fn gen_range<T, R>(&mut self, range: R) -> T
    where
        T: SampleUniform,
        R: SampleRange<T>,
    { ... }
    ...
}

看起來這個 traits 是定義了許多空函式,再到 traits 的文件與龍哥這篇比較淺顯易懂的介紹會得到幾個重點:

  • 如果學過其他物件導向語言,traits 類似 interfaceabstract class 的概念。白話地講的話就是定義好某個特徵函式,其他類別可以去自己實作自己要的樣子
  • 在 Rust 中類似於其他語言的 Class 概念被稱為 struct,可以用 impl…for 的語法來替每個類別去實現 (implement) 他的特徵

參考文章中的範例看個感覺:

// 定義一個「可飛行」的特徵
trait Flyable {
    fn fly(&self);
}

// 定義一個 Cat 類別
struct Cat {
    name: String,
}

// 為 Cat 實現「可飛行」的特徵
impl Flyable for Cat {
    fn fly(&self) {
        println!("看啊,那隻貓 {} 竟然會飛!", self.name);
    }
}

struct Bird {
    species: String,
}

impl Flyable for Bird {
    fn fly(&self) {
        println!("這隻 {} 正在飛行!", self.species);
    }
}

回到原本想了解的 rand::Rng 與下面的這段 let secret_number = rand::thread_rng().gen_range(1..=100);,可以知道其中的 thread_rng 是 rand 中的一個 struct,而其中有去實現了 gen_range 這個 trait

💡 順帶補充一下,其中的這個 [1..=100] 指的是範圍在 1~100 間,需要寫等號的原因是預設如果只寫 [1..100] 的話會不包含右邊界 (ref)。

什麼是 &mut

在這段程式中,我們會看到一個 &mut

let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("讀取該行失敗");

這是什麼意思呢?

先講個簡單的,這個 mut 在 Rust 中指的是 mutable 的意思,在 Rust 中宣告變數時,雖然常會用跟 JavaScript 有點像的 let 來宣告變數,但如果是要可變的話,就會需要這要寫:

fn main() {
    let foo = 5566;
    let mut bar = 1234;

    // 這行會導致編譯錯誤,因為 foo 是不可變的
    foo = 7788;

    // 這是允許的,因為 bar 有加上 mut
    bar = 7788;
}

而至於為什麼要放上 & 呢,這個就會牽扯到 Rust 的一大特性 —— 所有權 (Ownership),因為是大魔王,所以待我明天繼續來往下研究。

小結

今天效法 RBE (Rust By Example) 的精神,嘗試直接用一些簡單的範例來回頭學習語法,或許這對有其他語言底子或不想看整本 The Book 的人來說會是最快速的方式。

今天從一個 rand 的引入中就一次補齊了 structtraits 的基本概念,雖然其中還有許多進階內容,但先理解到是什麼、怎麼用後,未來有需要再回來補上。

參考資料


上一篇
Day 26:Rust 學習筆記 (3) - 用單元測試來學習 Rust 語法
下一篇
Day 28:Rust 中的所有權 (Ownership) 是什麼?(1)
系列文
Rust 的戰國時代:探索網頁前端工具的前世今生30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言